package hu.mapro.gamelib;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.ChainShape;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.World;

public class HorizontalFunctionLevel extends AbstractLinearLevel {

	public HorizontalFunctionLevel(
			World world,
			Camera camera,
			Function function, 
			float maxY, 
			float minY, 
			float sectionSize, 
			float visibilityRange
	) {
		super(minY, maxY);
		this.world = world;
		this.camera = camera;
		this.function = function;
		this.sectionSize = sectionSize;
		this.visibilityRange = visibilityRange;
		
		initPolygons();
		
		initDrawing();
		initWorld();
	}
	
	final World world;
	final Camera camera;
	Function function;
	float sectionSize;
	float visibilityRange;
	float generationUnit;

	int sectionCount;
	int borderCount;
	float[] polygonVertices;
	int verticesCount;
	short[] polygonTriangles;
	int triangleCount;
	//float sight;
	
	
	PolygonSpriteBatch polyBatch;
	Texture texture;

	int leftBorderIndex;
	int leftVertexIndex;
	int leftPolygonVerticesIndex;
	int leftPolygonTrianglesIndex;
	int rightBorderIndex;
	int rightVertexIndex;
	int rightPolygonVerticesIndex;
	int rightPolygonTrianglesIndex;
	private Body groundBody;
	private FixtureDef groundFixtureDef;
	private Fixture groundFixture;
	private float[] groundShapeChainVertices;
	private float generatedSize;

	void stepLeft() {
		leftBorderIndex = rightBorderIndex;
		leftVertexIndex = rightVertexIndex;
		leftPolygonVerticesIndex = rightPolygonVerticesIndex;
		leftPolygonTrianglesIndex = rightPolygonTrianglesIndex;
		
		rightBorderIndex-=1;
		rightVertexIndex-=2;
		rightPolygonVerticesIndex-=10;
		rightPolygonTrianglesIndex-=6;
		
		if (rightBorderIndex<0) {
			rightBorderIndex += borderCount;
			rightVertexIndex += verticesCount;
			rightPolygonVerticesIndex += polygonVertices.length;
		}
		
		if (rightPolygonTrianglesIndex<0) {
			rightPolygonTrianglesIndex += polygonTriangles.length;
		}
	}
	
	void stepRight() {
		rightBorderIndex = leftBorderIndex;
		rightVertexIndex = leftVertexIndex;
		rightPolygonVerticesIndex = leftPolygonVerticesIndex;
		rightPolygonTrianglesIndex = leftPolygonTrianglesIndex;
		
		leftBorderIndex+=1;
		leftVertexIndex+=2;
		leftPolygonVerticesIndex+=10;
		leftPolygonTrianglesIndex+=6;
		
		if (leftBorderIndex>=borderCount) {
			leftBorderIndex -= borderCount;
			leftVertexIndex -= verticesCount;
			leftPolygonVerticesIndex -= polygonVertices.length;
		}
		
		if (leftPolygonTrianglesIndex>=polygonTriangles.length) {
			leftPolygonTrianglesIndex -= polygonTriangles.length;
		}
	}

	public void initPolygons() {
		generatedSize = visibilityRange * 4;
		generationUnit = visibilityRange * 0.9f;
		sectionCount = (int) (generatedSize / sectionSize);
		borderCount = sectionCount + 1;
		//sight = (sectionCount-3)/2*sectionSize; // -3 is rounding security
		verticesCount = borderCount * 2;
		triangleCount = sectionCount * 2;
		
		polygonVertices = new float[verticesCount*5];
		polygonTriangles = new short[triangleCount*3];
		
		leftBorderIndex = 0;
		leftVertexIndex = 0;
		leftPolygonVerticesIndex = 0;
		leftPolygonTrianglesIndex = 0;

		rightBorderIndex = borderCount-1;
		rightVertexIndex = rightBorderIndex * 2;
		rightPolygonVerticesIndex = rightVertexIndex * 5;
		rightPolygonTrianglesIndex = (triangleCount-2) * 3;
		
		for (int i = 0 ; i < borderCount ; i++) {
			float x = (i-(borderCount/2))*sectionSize;
			
			polygonVertices[i*10+0] = x;
			polygonVertices[i*10+1] = function.value(x);
			polygonVertices[i*10+2] = Color.WHITE.toFloatBits();
			polygonVertices[i*10+3] = x; // TODO u v
			polygonVertices[i*10+4] = function.value(x);
			
			polygonVertices[i*10+5] = x;
			polygonVertices[i*10+6] = minLateral - 10;  // 10 is security
			polygonVertices[i*10+7] = Color.WHITE.toFloatBits();
			polygonVertices[i*10+8] = x; // TODO u v
			polygonVertices[i*10+9] = minLateral - 10;
		}

		for (int i = 0 ; i < sectionCount ; i++) {
			polygonTriangles[i*6+0] = (short) (i*2+0);
			polygonTriangles[i*6+1] = (short) (i*2+1);
			polygonTriangles[i*6+2] = (short) (i*2+2);
			
			polygonTriangles[i*6+3] = (short) (i*2+2);
			polygonTriangles[i*6+4] = (short) (i*2+1);
			polygonTriangles[i*6+5] = (short) (i*2+3);
		}
	}

	public void initWorld() {
		BodyDef groundBodyDef =new BodyDef();
		groundBodyDef.type = BodyType.StaticBody;
		
		groundBody = world.createBody(groundBodyDef);
		
		groundFixtureDef = new FixtureDef();
		groundFixtureDef.density = 0;
		groundFixtureDef.friction = 1;
		
		groundShapeChainVertices = new float[borderCount*2];
		
		createGroundFixture();
	}

	private void createGroundFixture() {
		ChainShape groundShape = new ChainShape();
		int idx = 0;
		int vidx = leftPolygonVerticesIndex;
		for (int i=0;i<borderCount;i++) {
			groundShapeChainVertices[idx++] = polygonVertices[vidx+0];
			groundShapeChainVertices[idx++] = polygonVertices[vidx+1];
			vidx+=10;
			if (vidx>=polygonVertices.length) vidx-=polygonVertices.length;
		}
		groundShape.createChain(groundShapeChainVertices);
		groundFixtureDef.shape = groundShape;
		groundFixture = groundBody.createFixture(groundFixtureDef);
		groundShape.dispose();
	}

	@Override
	public void update(float x) {
		boolean changed = false;
		
		float leftSight = x - visibilityRange;
		float leftX = polygonVertices[leftPolygonVerticesIndex+0];
		if (leftX > leftSight) {
			leftSight -= generationUnit;
			do {
				changed = true;
				
				int oldLeftVertexIndex = leftVertexIndex;
				stepLeft();
				
				leftX -= sectionSize;
				polygonVertices[leftPolygonVerticesIndex+0] = leftX;
				polygonVertices[leftPolygonVerticesIndex+1] = function.value(leftX);
				polygonVertices[leftPolygonVerticesIndex+5] = leftX;
				// TODO u v 
				
				polygonTriangles[leftPolygonTrianglesIndex+0] = (short) (leftVertexIndex+0);
				polygonTriangles[leftPolygonTrianglesIndex+1] = (short) (leftVertexIndex+1);
				polygonTriangles[leftPolygonTrianglesIndex+2] = (short) (oldLeftVertexIndex+0);
				
				polygonTriangles[leftPolygonTrianglesIndex+3] = (short) (oldLeftVertexIndex+0);
				polygonTriangles[leftPolygonTrianglesIndex+4] = (short) (leftVertexIndex+1);
				polygonTriangles[leftPolygonTrianglesIndex+5] = (short) (oldLeftVertexIndex+1);
			} while (leftX > leftSight);
		}

		float rightSight = x + visibilityRange;
		float rightX = polygonVertices[rightPolygonVerticesIndex+0];
		if (rightX < rightSight) {
			rightSight += generationUnit;
			do {
				changed = true;
				
				int oldRightVertexIndex = rightVertexIndex;
				stepRight();
				
				rightX += sectionSize;
				polygonVertices[rightPolygonVerticesIndex+0] = rightX;
				polygonVertices[rightPolygonVerticesIndex+1] = function.value(rightX);
				polygonVertices[rightPolygonVerticesIndex+5] = rightX;
				// TODO u v 
				
				polygonTriangles[rightPolygonTrianglesIndex+0] = (short) (rightVertexIndex+0);
				polygonTriangles[rightPolygonTrianglesIndex+1] = (short) (rightVertexIndex+1);
				polygonTriangles[rightPolygonTrianglesIndex+2] = (short) (oldRightVertexIndex+0);
				
				polygonTriangles[rightPolygonTrianglesIndex+3] = (short) (oldRightVertexIndex+0);
				polygonTriangles[rightPolygonTrianglesIndex+4] = (short) (rightVertexIndex+1);
				polygonTriangles[rightPolygonTrianglesIndex+5] = (short) (oldRightVertexIndex+1);
				
			} while (rightX < rightSight);
		}

		if (changed) {
			groundBody.destroyFixture(groundFixture);
			createGroundFixture();
		}
		
	}

	
	
	@Override
	public void render() {
		Gdx.gl.glClearColor(Color.CLEAR.r, Color.CLEAR.g, Color.CLEAR.b, Color.CLEAR.a);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
		
		polyBatch.setProjectionMatrix(camera.combined);
		polyBatch.begin();
		polyBatch.draw(texture, polygonVertices, 0, polygonVertices.length, polygonTriangles, 0, polygonTriangles.length);
		polyBatch.end();
	}

	private void initDrawing() {
		Pixmap pixmap = new Pixmap(1, 1, Format.RGBA8888);
		pixmap.setColor(Color.GRAY);
		pixmap.drawPixel(0, 0);
		texture = new Texture(pixmap);
		
		polyBatch = new PolygonSpriteBatch();
	}

	public static interface Function {
		float value(float input);
	}
	
}
